#pragma once
#include <BWAPI.h>
#include <BWTA.h>
#include <BaseManager.h>
#include <UnitGroupManager.h>
#include <map>
#include <sstream>
class InformationManager
{
  public:
    static InformationManager* create();
    static void destroy();
    void onUnitDiscover(BWAPI::Unit* unit);
    void onUnitEvade(BWAPI::Unit* unit);
    void onUnitDestroy(BWAPI::Unit* unit);
    BWAPI::Player* getPlayer(BWAPI::Unit* unit) const;
    BWAPI::UnitType getType(BWAPI::Unit* unit) const;
    BWAPI::Position getLastPosition(BWAPI::Unit* unit) const;
    int getLastSeenTime(BWAPI::Unit* unit) const;
    bool exists(BWAPI::Unit* unit) const;
    bool enemyHasBuilt(BWAPI::UnitType type) const;
    int getBuildTime(BWAPI::UnitType type) const;
    const std::set<BWTA::BaseLocation*>& getEnemyBases() const;
    void setBaseEmpty(BWTA::BaseLocation* base);
    void setBaseOccupied(BWTA::BaseLocation* base);
    int getUnitTypeCount(BWAPI::UnitType type);
    bool isCloseToBase(std::set<BWAPI::Unit*> units, BWTA::BaseLocation* baseLoc);
    bool isCloseToBase(BWAPI::Unit* unit, BWTA::BaseLocation* baseLoc);
    BWAPI::Position getUnitGroupPosition(std::set<BWAPI::Unit*> units);
    bool isEnemyBase (BWTA::BaseLocation* baseLoc);
    int getHealthyMutasCount ();
    bool hasThreateningEnemy(BWAPI::Unit* unit) const ;
    class Threat {
    public:
       // maps from unitID -> unit*
      std::map<int ,BWAPI::Unit*> enemyUnits;
      BWAPI::Position position;
      
      Threat( std::set<BWAPI::Unit*> units, BWAPI::Position pos){
        std::set<BWAPI::Unit*>::iterator itr;
        for (itr=units.begin(); itr!=units.end(); itr++){
          this->enemyUnits[(*itr)->getID()] = (*itr);
        }
        this->position = pos;
      };
     
      

      
      // allows calculation of a unit's average damage output (ground and air)
      // If a unit does not have any attack, its average damage output is zero.
      // CURRENTLY BUGGY! SOMETIMES WILL RETURN UNKNOWN UNIT TYPE WHEN CLEARLY
      // THE UNIT IS OF KNOWN TYPE (happened with: Photon Cannon, Drone..)
      static int getAverageDamageOutput (BWAPI::Unit* unit){
          int unitTotalDmg = 0; int numWeapons = 0;
          BWAPI::UnitType type = unit->getType();
          int weaponID = unit->getType().groundWeapon().getID();
          int damageAmount = unit->getType().groundWeapon().damageAmount();

          BWAPI::WeaponType groundWeapon = type.groundWeapon();
          if (groundWeapon != BWAPI::WeaponTypes::None){
            unitTotalDmg += groundWeapon.damageAmount();
            numWeapons++;
          }
          BWAPI::WeaponType airWeapon = type.airWeapon();
          if (airWeapon != BWAPI::WeaponTypes::None){
            unitTotalDmg += airWeapon.damageAmount();
            numWeapons++;
          }

          if (numWeapons != 0)
            return (int) (unitTotalDmg/numWeapons);
          else
            return 0;
      }
      /**
      Sorts Threats in the following hierarchy:
      1> Total Damage output (Ground + Air if applicable) // DISABLED -- BUGGY!
      2> Number of units ( less == lesser threat)
      3> Highest UnitID ( the bigger one is less dangerous -- this assumes they're 
      created later and hence are moving in later.)
      */
      bool operator<(const Threat& t) const {
        std::map<int, BWAPI::Unit*>::const_iterator iter;

        // Comparing DAMAGE OUTPUT ... CURRENTLY BUGGY FOR NO FREAKING REASON!
        // See bug detail at getAverageDamageOutput()
        //int thisTotalDamage = 0, 
        //    thatTotalDamage = 0;

        int thisMaxID = -1,
            thatMaxID = -1;
        for (iter = this->enemyUnits.begin(); iter != this->enemyUnits.end(); iter++){
          //thisTotalDamage += getAverageDamageOutput(iter->second);
          thisMaxID = (thisMaxID < iter->first) ? (iter->first) : thisMaxID;
        }
        for (iter = t.enemyUnits.begin(); iter != t.enemyUnits.end(); iter++){
          //thatTotalDamage += getAverageDamageOutput(iter->second);
          thatMaxID = (thatMaxID < iter->first) ? (iter->first) : thatMaxID;
        }
        /**
        
        if (thisTotalDamage < thatTotalDamage) {
          return true;
        }

        if (thisTotalDamage > thatTotalDamage) {
          return false;
        }
        */
        // next, comparing NUM UNITS...
        if (this->enemyUnits.size() > t.enemyUnits.size() ) {
          return true;
        }
        if (this->enemyUnits.size() < t.enemyUnits.size() ) {
          return false;
        }
        // if the same, then to differentiate, let's just assume the more newly
        // created is less dangerous. 
        
        if (thisMaxID > thatMaxID) {
          return true;
        }
        return false;

      }; 

    
      operator std::string() const{
        std::stringstream ss;
        std::map<int, BWAPI::Unit*>::const_iterator itr;
        
        for (itr=this->enemyUnits.begin(); itr!=this->enemyUnits.end(); itr++){
          ss << itr->second->getType().getName() << "[" << itr->first <<"], ";
        }
        ss << this->position.x() << "," << this->position.y();
        return ss.str();
      };

      std::set<BWAPI::Unit*> getEnemies () const{
        std::set<BWAPI::Unit*> result;
        for each (std::pair<int, BWAPI::Unit*> p in this->enemyUnits) {
          result.insert(p.second);
        }
        return result;
      }

    };

    std::list<InformationManager::Threat> getCurrentActiveThreats();
    UnitGroup getAllKnownEnemy();
    int getLastScouted(BWTA::BaseLocation* base);
    void setLastScouted(BWTA::BaseLocation* base, int time);

    // debug information on units ordered vs units built
    std::map<std::string, int> itemOrders;
    void addItemOrder(std::string name);
    void removeItemOrder(std::string name);
  private:
    InformationManager();
    ~InformationManager();
    class UnitData
    {
      public:
        UnitData();
        BWAPI::Position position;
        BWAPI::UnitType type;
        BWAPI::Player* player;
        int lastSeenTime;
        bool exists;
    };
    void updateBuildTime(BWAPI::UnitType type, int time);
    std::map<BWAPI::Unit*, UnitData> savedData;
    std::map<BWAPI::UnitType, int> buildTime;
    std::set<BWTA::BaseLocation*> enemyBases;
    std::map<BWTA::BaseLocation*,BWAPI::Unit*> enemyBaseCenters;
    std::set<BWTA::BaseLocation*> startLocationCouldContainEnemy;
    std::map<BWAPI::UnitType, std::set<BWAPI::Unit*> > enemyUnitList;

    // memoization of currently visible enemy units
    std::set<BWAPI::Unit*> visibleEnemyUnits;
    int healthyMutalisksCount;
    bool scoutedAnEnemyBase;

    // time since a building was last seen near a base
    std::map<BWTA::BaseLocation*, int> lastScouted;
};
extern InformationManager* TheInformationManager;
